home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 …ember: Reference Library / Apple Developer Reference Library (December 1999) (Disk 1).iso / pc / technical documentation / develop / develop issue 27 / develop issue 27 code / internet config assistant / toolkit / neotextbox.c < prev    next >
Encoding:
Text File  |  1996-06-30  |  12.3 KB  |  371 lines

  1. /********************************************************************************
  2.  
  3.   NeoTextBox.c - a replacement for TextBox
  4.  
  5.   Written by Bryan K. Ressler (Beaker)
  6.  
  7. ********************************************************************************/
  8. //#include "NTBDemo.h"
  9. #include "NeoTextBox.h"
  10. #include <Script.h>
  11. #include <TextUtils.h>
  12.  
  13. #include <Fonts.h>
  14. #include <TextEdit.h>
  15. #include <FixMath.h>
  16. #define gHasTrueType true
  17.  
  18. /** STATIC PROTOTYPES **********************************************************/
  19. static unsigned short NTBLineHeight(unsigned char const* theText,
  20.                                     unsigned long textLen,
  21.                                     const Rect* wrapBox,
  22.                                     short lhCode,
  23.                                     short* startY);
  24. static void NTBDraw(StyledLineBreakCode breakCode,
  25.                     unsigned char const* lineStart,
  26.                     long lineBytes,
  27.                     const Rect* wrapBox,
  28.                     short align,
  29.                     short curY,
  30.                     short boxWidth);
  31.  
  32. /********************************************************************************
  33.  *
  34.  * NTBLineHeight - figures line height
  35.  *
  36.  * Input:    theText        the entire text that was given to the NeoTextBox call
  37.  *            textLen        the length in bytes of the text
  38.  *            lhCode        the line height code that was passed to NeoTextBox
  39.  *            startY        VAR - we return the starting vertical pen location here
  40.  *
  41.  * Output:    returns the line height to use
  42.  *            
  43. ********************************************************************************/
  44. unsigned short NTBLineHeight(unsigned char const* theText,
  45.                              unsigned long textLen,
  46.                              const Rect* wrapBox,
  47.                              short lhCode,
  48.                              short* startY)
  49. {
  50.     short asc,
  51.     desc;                                        /* Used in the OutlineMetrics calls */
  52.     FontInfo fInfo;                                /* The old-style font information record */
  53.     Point frac;                                    /* The fraction for the TrueType calls */
  54.     unsigned short lineHeight;                    /* The return value */
  55.  
  56.     GetFontInfo(&fInfo);
  57.     if (lhCode < 0)
  58.     {
  59.  
  60.         /*
  61.           If the user has specified variable-height lines, we need to try
  62.           to determine the tallest ascent in the given text.  We can only
  63.           really do this if the font is a TrueType font.  Otherwise, we
  64.           punt and use old-fashioned GetFontInfo numbers.
  65.         */
  66.  
  67.         frac.h = frac.v = 1;
  68.         if (gHasTrueType && IsOutline(frac, frac))
  69.         {
  70.  
  71.             /*
  72.               At this point we know the current font is a TrueType font, so
  73.               we do an OutlineMetrics call with our full text.  It will put
  74.               the tallest character ascent into asc, and the deepest descent
  75.               into desc.  Then we choose between whichever's most between
  76.               the old-style ascent/descent and the numbers we get from
  77.               the OutlineMetrics call.
  78.             */
  79.  
  80.             OutlineMetrics((short)textLen, theText, frac, frac, &asc, &desc, nil, nil, nil);
  81.             lineHeight = MAXOF(fInfo.ascent, asc) + MAXOF(fInfo.descent, -desc) + fInfo.leading;
  82.             *startY = wrapBox->top + MAXOF(fInfo.ascent, asc);
  83.             *startY += fInfo.leading;
  84.  
  85.         }
  86.         else
  87.         {
  88.  
  89.             /*
  90.               At this point we know the current font isn't TrueType, so we
  91.               just use the old way of calculating line height.
  92.             */
  93.  
  94.             lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
  95.             *startY = wrapBox->top + fInfo.ascent + fInfo.leading;
  96.         }
  97.  
  98.     }
  99.     else if (lhCode == 0)
  100.     {
  101.  
  102.         /*
  103.           If the user has specified "default" line height, he just wants us
  104.           to get the line height from the FontInfo record.
  105.         */
  106.  
  107.         lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
  108.         *startY = wrapBox->top + fInfo.ascent + fInfo.leading;
  109.  
  110.     }
  111.     else
  112.     {
  113.  
  114.         /* If the user has provided a specific line height, we just trust
  115.           them.  We can't really generate too good of a starting vertical
  116.           coordinate, but we munge one together anyway.
  117.         */
  118.  
  119.         lineHeight = lhCode;
  120.         *startY = wrapBox->top + lhCode + fInfo.leading;
  121.  
  122.     }
  123.  
  124.     return (lineHeight);
  125. }
  126.  
  127. /********************************************************************************
  128.  *
  129.  * NTBDraw - draws a line with appropriate justification
  130.  *
  131.  * Input:    breakCode    the break code that was returned from StyledLineBreak
  132.  *            lineStart    pointer to the beginning of the text for the current line
  133.  *            lineBytes    the length in bytes of the the text for this line
  134.  *            wrapBox        the box within which we're wrapping
  135.  *            align        the text alignment as specified by the user
  136.  *            curY        our current vertical pen coordinate
  137.  *            boxWidth    the width of wrapBox (since NeoTextBox already calculated it)
  138.  *
  139.  * Output:    none (draws on the screen)
  140.  *            
  141. ********************************************************************************/
  142. void NTBDraw(StyledLineBreakCode breakCode,
  143.              unsigned char const* lineStart,
  144.              long lineBytes,
  145.              const Rect* wrapBox,
  146.              short align,
  147.              short curY,
  148.              short boxWidth)
  149. {
  150.     unsigned long blackLen;                        /* Length of non-white characters */
  151.     short slop;                                    /* Number of pixels of slop for full just */
  152.  
  153.     /*
  154.       The first thing we do here is determine the length of the "black" part
  155.       of the current line.  This excludes spaces, carriage returns, and other
  156.       white stuff depending on the language.  How do we know what to elim-
  157.       inate?  We DON'T!  So we ask our friend the Script Manager to do it
  158.       for us.  VisibleLength returns the number of bytes that we should use
  159.       for pixel width calculations.
  160.     */
  161.  
  162. #if !NuKernelBuild
  163.     blackLen = VisibleLength((Ptr)lineStart, lineBytes);
  164. #else
  165.     blackLen = lineBytes;
  166. #endif
  167.  
  168.     if (align == ntbJustFull)
  169.     {
  170.  
  171.         /*
  172.           For full justification, we need to calculate the "slop" space on
  173.           the line that's not filled up by the text.  Then we move to the
  174.           margin and get ready to draw BUT WAIT!  If this is the last line of
  175.           a paragraph, we need to draw it with whatever the system justifi-
  176.           cation is.  So if the break code indicates we're at the end of the
  177.           text, or we can find a carriage return there ourselves, we just
  178.           change the text alignment to the current default system text align-
  179.           ment and fall through without drawing.  If it's just another line
  180.           within a paragraph, we use the handy Script Manager routine DrawJust
  181.           to draw it justified.  In languages like Arabic, full justification
  182.           is performed differently than just spacing out the words.  DrawJust
  183.           handles cases like these correctly.
  184.  
  185.           Note that when we go looking for the carriage return at the end of
  186.           the line, it's okay to simply index to the last byte of the string.
  187.           This is because the carriage return character, 0x0d, is in the
  188.           control-code range, which is defined to never be the low byte of a
  189.           two byte character.
  190.         */
  191.  
  192.         slop = boxWidth - TextWidth(lineStart, 0, blackLen);
  193.         MoveTo(wrapBox->left, curY);
  194.         if (breakCode == smBreakOverflow || *(lineStart + (lineBytes - 1)) == kReturnChar)
  195.             align = GetSysDirection();
  196.         else
  197.             DrawJust((Ptr)lineStart, blackLen, slop);
  198.     }
  199.  
  200.     /*
  201.       For the rest of the text alignments (left, center, and right), we just
  202.       move the pen to the right place and draw the text with DrawText.  Note
  203.       that the alignments that could have come into the NeoTextBox call have
  204.       been resoved down to one of the four constants teForceLeft, teJustRight
  205.       teJustCenter, or ntbJustFull.
  206.     */
  207.  
  208.     switch (align)
  209.     {
  210.         case teForceLeft:
  211.         case teJustLeft:
  212.             MoveTo(wrapBox->left, curY);
  213.             break;
  214.         case teJustRight:
  215.             MoveTo(wrapBox->right - TextWidth(lineStart, 0, blackLen), curY);
  216.             break;
  217.         case teJustCenter:
  218.             MoveTo(wrapBox->left + (boxWidth - TextWidth(lineStart, 0, blackLen)) / 2, curY);
  219.             break;
  220.     }
  221.     if (align != ntbJustFull)
  222.         DrawText(lineStart, 0, lineBytes);
  223. }
  224.  
  225. /********************************************************************************
  226.  *
  227.  * NeoTextBox - word-wraps text inside a given box
  228.  *
  229.  * Input:    theText        the text we need to wrap
  230.  *            textLen        the length in bytes of the text
  231.  *            wrapBox        the box within which we're wrapping
  232.  *            align        the text alignment
  233.  *                            teForceLeft, teFlushLeft    left justified
  234.  *                            teJustCenter, teCenter        center justified
  235.  *                            teJustRight, teFlushRight    right justified
  236.  *                            ntbJustFull                    full justified
  237.  *                            teJustLeft, teFlushDefault    system justified
  238.  *            lhCode        the line height code that was passed to NeoTextBox
  239.  *                            < 0        variable - based on tallest character
  240.  *                            0        default - based on GetFontInfo
  241.  *                            > 0        fixed - use lhCode as the line height
  242.  *            endY        VAR - if non-nil, the vertical coord of the last line
  243.  *            lhUsed        VAR - if non-nil, the line height used to draw the text
  244.  *
  245.  * Output:    returns the number of line drawn total (even if they drew outside of
  246.  *            the boundries of wrapBox)
  247.  *            
  248. ********************************************************************************/
  249. short NeoTextBox(unsigned char const* theText,
  250.                  unsigned long textLen,
  251.                  const Rect* wrapBox,
  252.                  short align,
  253.                  short lhCode,
  254.                  short* endY,
  255.                  short* lhUsed)
  256. {
  257.     RgnHandle oldClip;                            /* Saved clipping region */
  258.     StyledLineBreakCode breakCode;                /* Returned code from StyledLineBreak */
  259.     Fixed fixedMax;                                /* boxWidth converted to fixed point */
  260.     Fixed wrapWid;                                /* Width to wrap to */
  261.     short boxWidth;                                /* Width of the wrapBox */
  262.     long lineBytes;                                /* Number of bytes in one line */
  263.     unsigned short lineHeight;                    /* Calculated line height */
  264.     short curY;                                    /* Current vertical pen location */
  265.     unsigned short lineCount;                    /* Number of lines we've drawn */
  266.     long textLeft;                                /* Pointer to remaining bytes of text */
  267.     unsigned char const* lineStart;                /* Pointer to beginning of a line */
  268.     unsigned char const* textEnd;                /* Pointer to the end of input text */
  269.  
  270.     /*
  271.       First, we save the old clipping region and clip to wrapBox.  Then, figure
  272.       the width of wrapBox, and make a fixed point version of it.  Also, resolve
  273.       the text alignment teFlushDefault to be the default system text alignment.
  274.     */
  275.  
  276.     GetClip((oldClip = NewRgn()));
  277.     ClipRect(wrapBox);
  278.     boxWidth = wrapBox->right - wrapBox->left;
  279.     fixedMax = Long2Fix((long)boxWidth);
  280.     if (align == teFlushDefault)
  281.         align = GetSysDirection();
  282.  
  283.     /*
  284.       Now we call NTBLineHeight to calculate the appropriate line height.  It
  285.       also figures our starting vertical pen location in curY based on the
  286.       line height and other metric parameters.  Clear lineCount, set
  287.       lineStart to point to the beginning of the text, calculate textEnd for
  288.       comparison to know when we're done, and preset textLeft to be all the
  289.       text.
  290.     */
  291.  
  292.     lineHeight = NTBLineHeight(theText, textLen, wrapBox, lhCode, &curY);
  293.     lineCount = 0;
  294.     lineStart = theText;
  295.     textEnd = theText + textLen;
  296.     textLeft = textLen;
  297.  
  298.     /*
  299.       This is the main wrap-and-draw loop.  I bet you never thought wrapping
  300.       text could be so easy...
  301.     */
  302.  
  303.     do
  304.     {
  305.  
  306.         /*
  307.           Every line, we have to preset lineBytes to something non-zero.
  308.           This tells StyledLineBreak that we're drawing the first format
  309.           run on the line (of course, for us, there's only ONE format run
  310.           total).  Also preset wrapWid.  StyledLineBreak will always modify
  311.           lineBytes (to tell you how many bytes are on this line), and will
  312.           modify wrapWid, so we have to reset them each line.
  313.         */
  314.  
  315.         lineBytes = 1;
  316.         wrapWid = fixedMax;
  317.  
  318.         breakCode = StyledLineBreak((Ptr)lineStart, textLeft, 0, textLeft, 0, &wrapWid, &lineBytes);
  319.  
  320.         /*
  321.           Now that the Script Manager has done all the really hard work for
  322.           us, we draw the line.  We already knew lineStart, StyledLineBreak
  323.           gave us lineBytes, which we pass to NTBDraw.  It'll handle the
  324.           different text alignments itself.
  325.         */
  326.  
  327.         NTBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
  328.  
  329.         /*
  330.           Now we advance our vertical position down by the height of one
  331.           line, advance lineStart by the number of bytes we just drew,
  332.           calculate a new textLeft, and increment our line count.
  333.         */
  334.  
  335.         curY += lineHeight;
  336.         lineStart += lineBytes;
  337.         textLeft -= lineBytes;
  338.         lineCount++;
  339.  
  340.     } while (lineStart < textEnd);
  341.  
  342.     /*
  343.       Well that was a job well done.  Let's return some useful values, too.
  344.       If the user gave pointers for endY and lhUsed, we stuff our ending
  345.       vertical coordinate and the line height we calculated into those,
  346.       respectively.  These allow the guy to put something after the wrapped
  347.       text (or at least know the right place to put it).
  348.     */
  349.  
  350.     if (endY)
  351.         *endY = curY - lineHeight;
  352.     if (lhUsed)
  353.         *lhUsed = lineHeight;
  354.  
  355.     /*
  356.       Finally, restore the clipping region, dispose of the region, and
  357.       return the TOTAL number of lines drawn (note that we didn't stop
  358.       drawing when curY advanced past wrapBox->bottom.  This way, the user
  359.       could tell that the text overflowed wrapBox.  If you want to know how
  360.       many lines fit, divide wrapBox by lhUsed.  This way, you get the best
  361.       of both worlds.
  362.     */
  363.  
  364.     SetClip(oldClip);
  365.     DisposeRgn(oldClip);
  366.  
  367.     return (lineCount);
  368. }
  369.  
  370.  
  371.